-
Notifications
You must be signed in to change notification settings - Fork 14.7k
[libc++] Optimize __hash_table::erase(iterator, iterator) #152471
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
philnik777
commented
Aug 7, 2025
You can test this locally with the following command:git-clang-format --diff HEAD~1 HEAD --extensions -- libcxx/include/__hash_table View the diff from clang-format here.diff --git a/libcxx/include/__hash_table b/libcxx/include/__hash_table
index 2f0f9457f..872fbd480 100644
--- a/libcxx/include/__hash_table
+++ b/libcxx/include/__hash_table
@@ -1854,7 +1854,7 @@ __hash_table<_Tp, _Hash, _Equal, _Alloc>::erase(const_iterator __first, const_it
// current node
__next_pointer __current = __first.__node_;
size_type __bucket_count = bucket_count();
- size_t __chash = std::__constrain_hash(__current->__hash(), __bucket_count);
+ size_t __chash = std::__constrain_hash(__current->__hash(), __bucket_count);
// find previous node
__next_pointer __before_first = __bucket_list_[__chash];
for (; __before_first->__next_ != __current; __before_first = __before_first->__next_)
|
@llvm/pr-subscribers-libcxx Author: Nikolas Klauser (philnik777) Changes
Full diff: https://github.com/llvm/llvm-project/pull/152471.diff 1 Files Affected:
diff --git a/libcxx/include/__hash_table b/libcxx/include/__hash_table
index dacc152030e14..2f0f9457f1416 100644
--- a/libcxx/include/__hash_table
+++ b/libcxx/include/__hash_table
@@ -1848,12 +1848,56 @@ __hash_table<_Tp, _Hash, _Equal, _Alloc>::erase(const_iterator __p) {
template <class _Tp, class _Hash, class _Equal, class _Alloc>
typename __hash_table<_Tp, _Hash, _Equal, _Alloc>::iterator
__hash_table<_Tp, _Hash, _Equal, _Alloc>::erase(const_iterator __first, const_iterator __last) {
- for (const_iterator __p = __first; __first != __last; __p = __first) {
- ++__first;
- erase(__p);
+ if (__first == __last)
+ return iterator(__last.__node_);
+
+ // current node
+ __next_pointer __current = __first.__node_;
+ size_type __bucket_count = bucket_count();
+ size_t __chash = std::__constrain_hash(__current->__hash(), __bucket_count);
+ // find previous node
+ __next_pointer __before_first = __bucket_list_[__chash];
+ for (; __before_first->__next_ != __current; __before_first = __before_first->__next_)
+ ;
+
+ __next_pointer __end = __last.__node_;
+
+ // If __before_first is in the same bucket, clear this bucket first without re-linking it
+ if (__before_first != __first_node_.__ptr() &&
+ std::__constrain_hash(__before_first->__hash(), __bucket_count) == __chash) {
+ while (__current != __end) {
+ if (auto __next_chash = std::__constrain_hash(__current->__hash(), __bucket_count); __next_chash != __chash) {
+ __chash = __next_chash;
+ break;
+ }
+ auto __next = __current->__next_;
+ __node_traits::deallocate(__node_alloc(), __current->__upcast(), 1);
+ __current = __next;
+ --__size_;
+ }
}
- __next_pointer __np = __last.__node_;
- return iterator(__np);
+
+ while (__current != __end) {
+ auto __next = __current->__next_;
+ __node_traits::deallocate(__node_alloc(), __current->__upcast(), 1);
+ __current = __next;
+ --__size_;
+
+ // When switching buckets, set the old bucket to be empty and update the next bucket to have __before_first as its
+ // before-first element
+ if (__next) {
+ if (auto __next_chash = std::__constrain_hash(__next->__hash(), __bucket_count); __next_chash != __chash) {
+ __bucket_list_[__chash] = nullptr;
+ __chash = __next_chash;
+ __bucket_list_[__chash] = __before_first;
+ }
+ }
+ }
+
+ // re-link __before_start with __last
+ __before_first->__next_ = __current;
+
+ return iterator(__last.__node_);
}
template <class _Tp, class _Hash, class _Equal, class _Alloc>
|
@@ -1848,12 +1848,56 @@ __hash_table<_Tp, _Hash, _Equal, _Alloc>::erase(const_iterator __p) { | |||
template <class _Tp, class _Hash, class _Equal, class _Alloc> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please also show the results for the other unordered containers, including the multi ones.
@@ -1848,12 +1848,56 @@ __hash_table<_Tp, _Hash, _Equal, _Alloc>::erase(const_iterator __p) { | |||
template <class _Tp, class _Hash, class _Equal, class _Alloc> | |||
typename __hash_table<_Tp, _Hash, _Equal, _Alloc>::iterator |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add a release note.
for (const_iterator __p = __first; __first != __last; __p = __first) { | ||
++__first; | ||
erase(__p); | ||
if (__first == __last) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add a summary of what this optimization is in the commit message.
for (; __before_first->__next_ != __current; __before_first = __before_first->__next_) | ||
; | ||
|
||
__next_pointer __end = __last.__node_; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
__next_pointer __end = __last.__node_; | |
__next_pointer __last_node = __last.__node_; |
end
is a bit confusing since it's not the end of the container.
break; | ||
} | ||
auto __next = __current->__next_; | ||
__node_traits::deallocate(__node_alloc(), __current->__upcast(), 1); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't you need to destroy the node here? You seem to only deallocate. If that's indeed a bug, I think we need a test that finds this issue.
|
||
__next_pointer __end = __last.__node_; | ||
|
||
// If __before_first is in the same bucket, clear this bucket first without re-linking it |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// If __before_first is in the same bucket, clear this bucket first without re-linking it | |
// If __before_first is in the same bucket (i.e. the first element we erase is not the first in its bucket), clear this bucket first without re-linking it |
} | ||
} | ||
|
||
// re-link __before_start with __last |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// re-link __before_start with __last | |
// re-link __before_first with __last |